/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package biofilter.records;

import biofilter.util.MappedPosition;
import biofilter.util.SequencePosition;

/**
 * A ScoredRegionRecord is an extension of a GFF or like position record 
 * specific to RNA-Seq data.
 * This record also contains analysis results for use over an algorithm's runtime
 * It obeys all of the position sort of laws that a GFFRecord does, but wrappes
 * the GFFRecord with additional information.
 *
 * @author Daniel Quest
 * @date   April 15 2010
 */
public class ScoredRegionRecord implements Record, MappedPosition, Comparable, java.io.Serializable {


    public ScoredRegionRecord(){
        init();
    }
    
    public enum Flag {GENE, DOWWNSTREAMGENE, INSUFFICENTDATA, COVEREDGAP, UNCOVEREDGAP}

    double totalBPMapped = 0.0;
    double totalOverRegion = 0.0;
    double totalPosOverRegion = 0.0;
    double totalNegOverRegion = 0.0;
    double minOverRegion = Double.MAX_VALUE;
    double maxOverRegion = 0;
    double minPosOverRegion = Double.MAX_VALUE;
    double maxPosOverRegion = 0;
    double minNegOverRegion = Double.MAX_VALUE;
    double maxNegOverRegion = 0;
    double averageOverRegion = 0.0;
    double averagePosOverRegion = 0.0;
    double averageNegOverRegion = 0.0;
    Flag flagValue = Flag.GENE;  //example flags could be DOWWNSTREAMGENE, INSUFFICENTDATA ect.

    int longestGap = 0;
    int longestPosGap = 0;
    int longestNegGap = 0;

    public void init(){
        totalOverRegion = 0.0;
        totalPosOverRegion = 0.0;
        totalNegOverRegion = 0.0;
        minOverRegion = Double.MAX_VALUE;
        maxOverRegion = 0;
        minPosOverRegion = Double.MAX_VALUE;
        maxPosOverRegion = 0;
        minNegOverRegion = Double.MAX_VALUE;
        maxNegOverRegion = 0;
        averageOverRegion = 0.0;
        averagePosOverRegion = 0.0;
        averageNegOverRegion = 0.0;

        longestGap = 0;
        longestPosGap = 0;
        longestNegGap = 0;
    }
    
    public String formatDebug(){
        String s = "";
        s += "****************BEGIN REGION***************************************************\n";
        s += record.get();
        s += '\n';
        s += "totalBPMapped: " + totalBPMapped + "\n";
        s += "totalOverRegion: " + totalOverRegion + "\t+:" + totalPosOverRegion + "\t-:" + totalNegOverRegion + "\n";
        s += "maxOverRegion: " + maxOverRegion + "\t+:" + maxPosOverRegion + "\t-:" + maxNegOverRegion + "\n";
        s += "minOverRegion: " + minOverRegion + "\t+:" + minPosOverRegion + "\t-:" + minNegOverRegion + "\n";
        s += "averageOverRegion: " + averageOverRegion + "\t+:" + averagePosOverRegion + "\t-:" + averageNegOverRegion + "\n";
        s += "longestGap: " + longestGap + "\t+:" + longestPosGap + "\t-:" + longestNegGap + "\n";
        s += "FlagValue: " + flagValue.toString() + "\n";
        s += "****************END REGION*****************************************************\n";
        return s;
    }

    public SequencePosition castMappedPositionasSequencePosition(MappedPosition mp){
       return new SequencePosition(mp.getStart(), mp.getEnd(), mp.getStrand());
    }

    public double scoreRegion(HistogramRecord hr){
        totalBPMapped = hr.getBPAdded();
        int count = 0;
        init();
        int currentGap = 0;
        int currentPosGap = 0;
        int currentNegGap = 0;
        for(int i = this.getStart(); i<= this.getEnd(); i++){
            count++;
            double pos = hr.getPosition(i, '+');
            double neg = hr.getPosition(i, '-');
            totalOverRegion += pos + neg;
            totalPosOverRegion += pos;
            totalNegOverRegion += neg;
            if(pos <= minOverRegion) minOverRegion = pos;
            if(neg <= minOverRegion) minOverRegion = neg;
            if(pos >= maxOverRegion) maxOverRegion = pos;
            if(neg >= maxOverRegion) maxOverRegion = neg;
            if(pos < minPosOverRegion) minPosOverRegion = pos;
            if(pos > maxPosOverRegion) maxPosOverRegion = pos;
            if(neg < minNegOverRegion) minNegOverRegion = neg;
            if(neg > maxNegOverRegion) maxNegOverRegion = neg;


            //GAPS
            //
            //Calculate the size of the largest positive gap in the region
            if(pos == 0 && currentPosGap > 0){ currentPosGap++; } //gap is open, extend it
            else if( pos == 0 ) { currentPosGap = 1;  } //open a gap
            else { currentPosGap = 0; } //close a gap
            if( currentPosGap > this.longestPosGap ) longestPosGap = currentPosGap;

            //Calculate the size of the largest negative gap in the region
            if(neg == 0 && currentNegGap > 0){ currentNegGap++; } //gap is open, extend it
            else if( neg == 0 ) { currentNegGap = 1;  } //open a gap
            else { currentNegGap = 0; } //close a gap
            if( currentNegGap > this.longestNegGap ) longestNegGap = currentPosGap;

            if(this.longestPosGap > this.longestGap) this.longestGap = this.longestPosGap;
            if(this.longestNegGap > this.longestGap) this.longestGap = this.longestNegGap;
        }
        averageOverRegion = totalOverRegion / (2 * this.length());
        averagePosOverRegion = totalPosOverRegion / (this.length());
        averageNegOverRegion =  totalNegOverRegion / (this.length());

        return averageOverRegion;
    }
    
   private double average(MappedPosition mp, double rawCount){
        //. means add both the positive and negative strand to raw count
        double score = rawCount / (2 * mp.length());  //no global scaling
        return score;
    }

   /**
    public double scoreertsRegion(HistogramRecord hr){
        totalBPMapped = hr.getBPAdded();
        int count = 0;
        init();
        for(int i = this.getStart(); i<= this.getEnd(); i++){
            count++;

        }
        averageOverRegion = totalOverRegion / (2 * this.length());
        averagePosOverRegion = totalPosOverRegion / (this.length());
        averageNegOverRegion =  totalNegOverRegion / (this.length());

        return averageOverRegion;
    }
    */

   public boolean isType(String type){
       if (this.getGFF().getType().equalsIgnoreCase( type )) return true;
       return false;
   }

    public boolean flagedAsLowCoverage(){
        if(this.flagValue == Flag.INSUFFICENTDATA) return true;
        else return false;
    }

    public boolean overlaps(ScoredRegionRecord s){
        SequencePosition s1 = new SequencePosition(this.getStart(), this.getEnd(), this.getStrand());
        SequencePosition s2 = new SequencePosition(s.getStart(), s.getEnd(), s.getStrand());
        return s1.overlaps(s2);
    }

    private boolean codirectional(ScoredRegionRecord s1, ScoredRegionRecord s2){
        if( s1.getStrand() == s2.getStrand() ) return true;
        //if either region is unstranded... then it can be codirectional
        if( s1.getStrand() == '.' ) return true;
        if( s2.getStrand() == '.' ) return true;
        return false;
    }
    /**
     * Assumes s1 and s2 are coding sequences.
     * Definition: a coding sequence, A, is upstream of another coding sequence, B
     * if:
     * 1) A starts before B on the genome and A and B are both on the positive strand or
     * 2) B starts before A on the genome and they are both on the negative strand.
     *
     *
     * @param s1 - a region of DNA
     * @param s2 - a second region of DNA
     * @return true, s1 is upstream of s2, false otherwise
     */
    private boolean upstreamGene(ScoredRegionRecord s1, ScoredRegionRecord s2){
        if( s1.getStrand() == '+' && s2.getStrand() == '+' && s1.getStart() < s2.getStart() ) return true;
        if( s1.getStrand() == '-' && s2.getStrand() == '-' && s1.getStart() > s2.getStart() ) return true;
        return false;
    }

   /**
    * Begin getters and setters for the mappedPosion, Record, and Comparable interfaces
    * Also these functions may access some interal values in the GFFRecord.
    */
    GFFRecord record = null;
    public String get() {
        return record.get();
    }

    public void set(String r) throws IllegalArgumentException {
        record.set(r);
    }

    public void setGFF(GFFRecord g){
        this.record = g;
    }

    public GFFRecord getGFF(){
        return this.record;
    }

    String getSource() {
        return this.getGFF().getSource();
    }
    
    public String getSeqid(){
        return this.record.getSeqid();
    }

    public int getStart() {
        return record.getStart();
    }

    public int getEnd() {
        return record.getEnd();
    }

    public int length() {
        return record.length();
    }

    public char getStrand() {
        return record.getStrand();
    }

    public double getWeight() {
        return record.getWeight();
    }

    public String getQueryID() {
        return record.getQueryID();
    }

    public String getSubjectID() {
        return record.getSubjectID();
    }

    public int compareTo(Object arg0) {
        ScoredRegionRecord s = (ScoredRegionRecord) arg0;
        return record.compareTo(s.record);
    }

    public void setScore(double score){
        record.setScore(score);
    }

    public double getScore(){
        return record.getScore();
    }

    /**
     * Begin geters and setters for the scoring regions...
     * @return
     */
    public double getAverageNegOverRegion() {
        return averageNegOverRegion;
    }

    public void setAverageNegOverRegion(double averageNegOverRegion) {
        this.averageNegOverRegion = averageNegOverRegion;
    }

    public double getAverageOverRegion() {
        return averageOverRegion;
    }

    public void setAverageOverRegion(double averageOverRegion) {
        this.averageOverRegion = averageOverRegion;
    }

    public double getAveragePosOverRegion() {
        return averagePosOverRegion;
    }

    public void setAveragePosOverRegion(double averagePosOverRegion) {
        this.averagePosOverRegion = averagePosOverRegion;
    }

    public Flag getFlagValue() {
        return flagValue;
    }

    public void setFlagValue(Flag flagValue) {
        this.flagValue = flagValue;
    }

    public double getMaxNegOverRegion() {
        return maxNegOverRegion;
    }

    public void setMaxNegOverRegion(double maxNegOverRegion) {
        this.maxNegOverRegion = maxNegOverRegion;
    }

    public double getMaxOverRegion() {
        return maxOverRegion;
    }

    public void setMaxOverRegion(double maxOverRegion) {
        this.maxOverRegion = maxOverRegion;
    }

    public double getMaxPosOverRegion() {
        return maxPosOverRegion;
    }

    public void setMaxPosOverRegion(double maxPosOverRegion) {
        this.maxPosOverRegion = maxPosOverRegion;
    }

    public double getMinNegOverRegion() {
        return minNegOverRegion;
    }

    public void setMinNegOverRegion(double minNegOverRegion) {
        this.minNegOverRegion = minNegOverRegion;
    }

    public double getMinOverRegion() {
        return minOverRegion;
    }

    public void setMinOverRegion(double minOverRegion) {
        this.minOverRegion = minOverRegion;
    }

    public double getMinPosOverRegion() {
        return minPosOverRegion;
    }

    public void setMinPosOverRegion(double minPosOverRegion) {
        this.minPosOverRegion = minPosOverRegion;
    }

    public GFFRecord getRecord() {
        return record;
    }

    public void setRecord(GFFRecord record) {
        this.record = record;
    }

    public double getTotalBPMapped() {
        return totalBPMapped;
    }

    public void setTotalBPMapped(double totalBPMapped) {
        this.totalBPMapped = totalBPMapped;
    }

    public double getTotalNegOverRegion() {
        return totalNegOverRegion;
    }

    public void setTotalNegOverRegion(double totalNegOverRegion) {
        this.totalNegOverRegion = totalNegOverRegion;
    }

    public double getTotalOverRegion() {
        return totalOverRegion;
    }

    public void setTotalOverRegion(double totalOverRegion) {
        this.totalOverRegion = totalOverRegion;
    }

    public double getTotalPosOverRegion() {
        return totalPosOverRegion;
    }

    public void setTotalPosOverRegion(double totalPosOverRegion) {
        this.totalPosOverRegion = totalPosOverRegion;
    }

    public int getLongestGap() {
        return longestGap;
    }

    public int getLongestNegGap() {
        return longestNegGap;
    }

    public int getLongestPosGap() {
        return longestPosGap;
    }

}
